package src;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

/**
 * 
 * @author Patrik Mihalcin
 */
public class Context {

	private int attributesCount = 0;
	private int objectsCount = 0;
	private Logic logic;
	private Double[][] relation;
	
	private Set<Concept> concepts = new HashSet<Concept>();
	
	private Map<List<Double>, List<List<Double>>> upperNeighbors = new HashMap<List<Double>, List<List<Double>>>();
	private Map<List<Double>, List<List<Double>>> lowerNeighbors = new HashMap<List<Double>, List<List<Double>>>();
	
	private List<List<Double>> extents = new ArrayList<List<Double>>();
	private List<List<Double>> intents = new ArrayList<List<Double>>();
	
	public Context(Integer logicDegree, Double[][] relation) {
		this.logic = new Logic(logicDegree);
		this.relation = relation;
		
		objectsCount = relation.length;
		attributesCount = relation[0].length;	
	}
	
	@Override
	public String toString() {
		
		String result = "";
		
		for (int i = 0; i < relation.length; i++) {
			for (int j = 0; j < relation[0].length; j++) {
				result += relation[i][j] + " ";
			}
			result += "\n";
		}
		
		return result;
	}
	
	public Context(Integer logicDegree, List<List<Double>> relation) {
		this.logic = new Logic(logicDegree);
		
		objectsCount = relation.size();
		attributesCount = relation.get(0).size();		
		
		Double[][] array = new Double[objectsCount][attributesCount];
		
		for (int i = 0; i < objectsCount; i++) {
			for (int j = 0; j < attributesCount; j++) {
				array[i][j] = relation.get(i).get(j);
			}
		}
		
		this.relation = array;		
	}
	
//	context operators
	public List<Double> down(List<Double> b) { 
//		intent
		if (!(attributesCount == b.size())) {
			throw new ContextOperatorException(attributesCount, b.size());
		}
		List<Double> a = new ArrayList<Double>();
		for (int bb = 0; bb < objectsCount; bb++) {
			Double min = Double.MAX_VALUE;
			for (int aa = 0; aa < attributesCount; aa++) {
				Double value = null;
				try {
					value = logic.implication(b.get(aa), relation[bb][aa]);					
				} catch (IndexOutOfBoundsException e) {
					throw new ContextOperatorException(attributesCount, b.size());
				} 
				min = logic.infimum(value, min);
			}
			a.add(min);
		}
		return a;
	}

	public List<Double> up(List<Double> a) {
//		extent
		if (!(objectsCount == a.size())) {
			throw new ContextOperatorException(objectsCount, a.size());
		}
		List<Double> b = new ArrayList<Double>();
		for (int aa = 0; aa < attributesCount; aa++) {
			Double min = Double.MAX_VALUE;
			for (int bb = 0; bb < objectsCount; bb++) {
				Double value = null;
				try {
					value = logic.implication(a.get(bb), relation[bb][aa]);					
				} catch (IndexOutOfBoundsException e) {
					throw new ContextOperatorException(objectsCount, b.size());
				}
				min = logic.infimum(value, min);
			}
			b.add(min);
		}
		return b;
	}
	
	public List<Double> closure(List<Double> set, ClosureType closureType) {
		if (closureType == ClosureType.DOWN_UP) {
			return up(down(set));
		} 		
		return down(up(set));
	}

	private List<Double> generatorOfUpperNeighbors(Integer index, List<Double> set, ClosureType closureType) {
//		index in fuzzy set b, where truth value will be replaced with next higher truth value
		List<Double> result = new ArrayList<Double>(set);
		ListIterator<Double> it = result.listIterator();
		
//		find out where next truth value should be set using ListIterator
		index++;
		Double value = null;
		while (index-- > 0) {
			value = it.next();
		}
		
//		set next higher truth value
		it.set(logic.getNextTruthValue(value));		
		
//		do closure
		return closure(result, closureType);
	}

	private List<Attribute> minimalAttributeSet(List<Double> b) {
//		return list of attributes which has value different from 1.0
		List<Attribute> result = new ArrayList<Attribute>();
		int y = 0;
		for (ListIterator<Double> it = b.listIterator(); it.hasNext();) {
			if (!it.next().equals(1.0)) {
				result.add(new Attribute(y, b.get(y)));
			}
			y++;
		}
		return result;
	}
	
	private List<Objekt> minimalObjectSet(List<Double> a) {
//		return list of objects which has value different from 1.0
		List<Objekt> result = new ArrayList<Objekt>();
		int x = 0;
		for (ListIterator<Double> it = a.listIterator(); it.hasNext();) {
			if (!it.next().equals(1.0)) {
				result.add(new Objekt(x, a.get(x)));
			}
			x++;
		}
		return result;
	}

	private List<Attribute> increasedAttributes(int y, List<Double> b, List<Double> d) {
//		return list of increased attributes (apart from attribute y) comparing 2 fuzzy sets 
		List<Attribute> result = new ArrayList<Attribute>();
		for (int z = 0; z < b.size(); z++) {
			if (z != y) {
				if (b.get(z) < d.get(z)) {
					result.add(new Attribute(z, b.get(z)));
				}
			}
		}
		return result;
	}
	
	private List<Objekt> increasedObjects(int x, List<Double> a, List<Double> d) {
//		return list of increased objects (apart from object x) comparing 2 fuzzy sets 
		List<Objekt> result = new ArrayList<Objekt>();
		for (int z = 0; z < a.size(); z++) {
			if (z != x) {
				if (a.get(z) < d.get(z)) {
					result.add(new Objekt(z, a.get(z)));
				}
			}
		}
		return result;
	}

	private List<Attribute> intersectionAttributes(List<Attribute> min, List<Attribute> increased) {
		List<Attribute> result = new ArrayList<Attribute>();
		for (Attribute atribut1 : increased) {
			for (Attribute atribut2 : min) {
				if (atribut1.compareTo(atribut2) == 1) {
					result.add(atribut1);
				}
			}
		}
		return result;
	}
	
	private List<Objekt> intersectionObjects(List<Objekt> min, List<Objekt> increased) {
		List<Objekt> result = new ArrayList<Objekt>();
		for (Objekt objekt1 : increased) {
			for (Objekt objekt2 : min) {
				if (objekt1.compareTo(objekt2) == 1) {
					result.add(objekt1);
				}
			}
		}
		return result;
	}

	private List<List<Double>> neighbors(List<Double> set, ClosureType closureType) { 
//		compute upper neigbors for each newly added fixpoint and update the info about lower neigbors
		List<List<Double>> result = new ArrayList<List<Double>>();
		
		if (closureType == ClosureType.DOWN_UP) {
			List<Attribute> min = minimalAttributeSet(set);	
			for (Iterator<Attribute> it = min.iterator(); it.hasNext();) {
				Attribute y = it.next();
				List<Double> d = generatorOfUpperNeighbors(y.getIndex(), set, closureType);
				List<Attribute> increased = increasedAttributes(y.getIndex(), set, d);
				if (intersectionAttributes(min, increased).isEmpty()) {
					result.add(d);
				} else {
					it.remove();
				}
			}
		} else if (closureType == ClosureType.UP_DOWN) {
			List<Objekt> min = minimalObjectSet(set);
			for (Iterator<Objekt> it = min.iterator(); it.hasNext();) {
				Objekt x = it.next();
				List<Double> d = generatorOfUpperNeighbors(x.getIndex(), set, closureType);
				List<Objekt> increased = increasedObjects(x.getIndex(), set, d);
				if (intersectionObjects(min, increased).isEmpty()) {
					result.add(d);
				} else {
					it.remove();
				}
			}
		}			
		
		return result;
	}
	
	private void generateFrom(List<Double> set, List<List<Double>> f, ClosureType closureType) {
//		generates fixpoints
		List<List<Double>> neighbors = neighbors(set, closureType);	
		
//		be careful when you are adding lists to arraylist in Map
//		workaround using for
//		update upperNeighbors
		upperNeighbors.put(set, new ArrayList<List<Double>>());
		for (List<Double> neighbor : neighbors) {
			upperNeighbors.get(set).add(neighbor);
		}
		
		for (List<Double> d : neighbors) {	
			if (!lowerNeighbors.keySet().contains(d)) {
				lowerNeighbors.put(d, new ArrayList<List<Double>>());	
			}						
			lowerNeighbors.get(d).add(set);
		}
		neighbors.removeAll(f);
	
		for (List<Double> d : neighbors) {			
			if (neighbors.contains(d)) {
				f.add(d);
			}
		}
		for (List<Double> d : neighbors) {
			generateFrom(d, f, closureType);
		}
	}

	public void lattice(ClosureType closureType) {
		concepts.clear();
		upperNeighbors.clear();
		lowerNeighbors.clear();
		extents.clear();
		intents.clear();
		
		List<List<Double>> f = new ArrayList<List<Double>>();
		List<Double> emptySet = new ArrayList<Double>();
		
//		we start with the least fixpoint of fuzzy closure operator, which is closure of empty set
		if (closureType == ClosureType.DOWN_UP) {	
			for (int i = 0; i < attributesCount; i++) {
				emptySet.add(0.0);
			}
		} else if (closureType == ClosureType.UP_DOWN) {
			for (int i = 0; i < objectsCount; i++) {
				emptySet.add(0.0);
			}
		}
//		do closure
		List<Double> set = closure(emptySet, closureType);
//		closure of empty set has no lower neighbor
		lowerNeighbors.put(set, new ArrayList<List<Double>>());
		
		f.add(set);
		generateFrom(set, f, closureType);		
		
		if (closureType == ClosureType.DOWN_UP) {		
			for (List<Double> intent : f) {
				concepts.add(new Concept(down(intent), intent));
				intents.add(intent);
				extents.add(down(intent));
			}
		} else if (closureType == ClosureType.UP_DOWN) {
			for (List<Double> extent : f) {
				concepts.add(new Concept(extent, up(extent)));
				extents.add(extent);
				intents.add(up(extent));
			}
		}
	}

	public Set<Concept> getConcepts() {
		return concepts;
	}
	
	public Map<List<Double>, List<List<Double>>> getLowerNeighbors() {
		return lowerNeighbors;
	}
	
	public Map<List<Double>, List<List<Double>>> getUpperNeighbors() {
		return upperNeighbors;
	}
	
	public List<List<Double>> getExtents() {
		return extents;
	}
	
	public List<List<Double>> getIntents() {
		return intents;
	}
	
	public int getAttributesCount() {
		return attributesCount;
	}
	
	public int getObjectsCount() {
		return objectsCount;
	}
	
	public Double[][] getRelation() {
		return relation;
	}
	
	public Logic getLogic() {
		return logic;
	}
	
	public static void main(String[] args) {
//		Double[][] relation = {{ 1.0, 0.0, 0.0, 1.0 },			
//								{ 1.0, 0.0, 0.0, 1.0 },		
//								{ 1.0, 0.0, 1.0, 1.0 },
//								{ 1.0, 0.0, 0.0, 1.0 },
//								{ 0.0, 0.0, 1.0, 1.0 }};
//		Context c = new Context(2, relation);
//		c.lattice(ClosureType.UP_DOWN);
//		
//		System.out.println("LOWER NEIGHBORS:");
//		for (Entry<List<Double>, List<List<Double>>> entry : c.getLowerNeighbors().entrySet()) {
//			System.out.println("lower neighbors of " + entry.getKey() + ":");
//			System.out.println(entry.getValue());
//		}
//		System.out.println();
//		
//		System.out.println("UPPER NEIGHBORS:");
//		for (Entry<List<Double>, List<List<Double>>> entry : c.getUpperNeighbors().entrySet()) {
//			System.out.println("upper neighbors of " + entry.getKey() + ":");
//			System.out.println(entry.getValue());
//		}
//		System.out.println();
//		
//		System.out.println("CONCEPTS:");
//		for (Concept concept : c.getConcepts()) {
//			System.out.println(concept);
//		}		
//		System.out.println();
		
		Double[][] relation2 = {{ 1.00, 0.50, 0.50, 1.00 },			
								{ 1.00, 0.25, 0.25, 1.00 },		
								{ 1.00, 1.00, 1.00, 1.00 }};
		
		Context c2 = new Context(5, relation2);
		c2.lattice(ClosureType.UP_DOWN);
		
		System.out.println("LOWER NEIGHBORS:");
		for (Entry<List<Double>, List<List<Double>>> entry : c2.getLowerNeighbors().entrySet()) {
		System.out.println("lower neighbors of " + entry.getKey() + ":");
		System.out.println(entry.getValue());
		}
		System.out.println();
		
		System.out.println("UPPER NEIGHBORS:");
		for (Entry<List<Double>, List<List<Double>>> entry : c2.getUpperNeighbors().entrySet()) {
		System.out.println("upper neighbors of " + entry.getKey() + ":");
		System.out.println(entry.getValue());
		}
		System.out.println();
		
		System.out.println("CONCEPTS:");
		for (Concept concept : c2.getConcepts()) {
		System.out.println(concept);
		}		
		System.out.println();

		
//		Double[] fuzzySet = {1.0, 1.0, 1.0, 1.0, 1.0};
//		System.out.println(c.up(Arrays.asList(fuzzySet)));
//		
//		Double[] fuzzySet2 = {0.0, 0.0, 0.0, 1.0};
//		System.out.println(c.down(Arrays.asList(fuzzySet2)));
	}
}